#include "simulation.h"

WebServerSimulation::WebServerSimulation(int argc, char **argv) : Simulation() {
    kSeed_arrival = time(NULL);             //seed for the generating the interarrival times
    kSeed_service = time(NULL) + 10467;     //seed for the generating the service times

    //command line inputs and validations
    lambda = atof(argv[1]);     //0 < lambda < 1
    if (lambda > 1) {
        cout << "Invalid value of lambda. lambda must be in (0, 1)" << endl;
        return;
    }
    mu = 1;                     //mu = 1 always
    kcpu = atoi(argv[2]);
    kio = atoi(argv[3]);
    if (kcpu <= 0 || kio <= 0) {
        cout << "Invalid value of K_CPU or K_IO" << endl;
        return;
    }
    c = atoi(argv[4]);
    if (c <= 0) {
        cout << "Invalid value of C. C must be greater than 0" << endl;
        return;
    }
    l = atoi(argv[5]);
    if (! (l == 0 || l == 1)) {
        cout << "Invalid value of L" << endl;
        exit(0);
    }
    m = atoi(argv[6]);
    if (m < 0 || m > 5) {
        cout << "Invalid value of M" << endl;
        exit(0);
    }

    Ta = 200.0;
    Ts = 100.0;
    te = 1<<30;
    n = 0;
    t1 = expntl(1/lambda, kSeed_arrival);
    t2 = te;
    sim_time = 0.0; 

    serve_count = 0;
    master_clock = 0;

    arrival_count = 0;
    departure_count = 0;
    total_arrival_count = 0;
    total_departure_count = 0;

    mu_io[0] = mu_io[1] = mu_io[2] = 0.5;
    capacity[0] = capacity[1] = capacity[2] = 0;
    kSeed_service_io[0] = time(NULL) + 6034;
    kSeed_service_io[1] = time(NULL) + 52513;
    kSeed_service_io[2] = time(NULL) + 23821;
	kSeed_selector = time(NULL) - 9502;

    //initiate the arrival of the first customer, generate the corresponding event,
    //add it to the event list and add the customer to the customer_list
    //set simulation time
    Ts = expntl(1/mu, kSeed_service);
    Customer *customer = new Customer(total_arrival_count++, t1, Ts, 0.0, 0.0, false, false);
    customer->queue_ = &customer_queue;
    customer->queue_type_ = QUEUE_CPU;
    EventNode *next_event = new EventNode('A', customer, t1);
    event_list_add(next_event);
    sim_time = t1;
    customer_list.push_back(customer);
}

//schedule a departure event for a given customer
void WebServerSimulation::schedule_departure(Customer *customer) {
    //set the service time
    if (customer->queue_type_ == QUEUE_CPU) {
        Ts = expntl(1/mu, kSeed_service);
    } else if (customer->queue_type_ == QUEUE_IO1) {
        Ts = expntl(1/mu_io[0], kSeed_service_io[0]);
    } else if (customer->queue_type_ == QUEUE_IO2) {
        Ts = expntl(1/mu_io[1], kSeed_service_io[1]);
    } else if (customer->queue_type_ == QUEUE_IO3) {
        Ts = expntl(1/mu_io[2], kSeed_service_io[2]);
    }

    customer->service_time_ = Ts;
    //create a departure event and add it to the event list
    EventNode *current_departure = new EventNode('D', customer, sim_time + Ts);
    event_list_add(current_departure);
}


void WebServerSimulation::process_arrival(EventNode *next_event) {
    Ta = expntl(1/lambda, kSeed_arrival);
    sim_time = next_event->time_;
    if (next_event->customer_->queue_type_ == QUEUE_CPU) {
        if (n < kcpu) {
            next_event->customer_->arrival_time_ = sim_time;
            next_event->customer_->customer_stats_.push_back(new CustomerStats(QUEUE_CPU, sim_time, 0.0, 0.0, 0.0, false));
            arrival_count++;
            customer_queue.push_back(next_event->customer_);

            cpu_stats.push_back(new QueueStats('A', next_event->customer_->id_,
                                               0.0, 0.0, 0.0, false));

            n++;
            if (n == 1) {
                schedule_departure(customer_queue.front());
            }
            if (! next_event->customer_->readd_) {
                Customer *customer = new Customer(total_arrival_count++, sim_time + Ta, 0.0, 0.0, 0.0, false, false);
                customer_list.push_back(customer);
                EventNode *next_arrival = new EventNode('A', customer, sim_time + Ta);
                event_list_add(next_arrival);
            }
        } else {
            //cout << "CPU lost" << endl;
            next_event->customer_->customer_stats_.push_back(new CustomerStats(QUEUE_CPU, sim_time, 0.0, 0.0, 0.0, true));
            next_event->customer_->lost_ = true;
            cpu_stats.push_back(new QueueStats('L', next_event->customer_->id_,
                                               0.0, 0.0, 0.0, true));
            EventNode *lost_arrival = new EventNode('L', next_event->customer_, sim_time);
            event_list_add(lost_arrival);
        }
    } else if (next_event->customer_->queue_type_ == QUEUE_IO1) {
        if (capacity[0] < kio) {
            next_event->customer_->arrival_time_ = sim_time;
            next_event->customer_->customer_stats_.push_back(new CustomerStats(QUEUE_IO1, sim_time, 0.0, 0.0, 0.0, false));
            io_queue_1.push_back(next_event->customer_);
            io_1_stats.push_back(new QueueStats('A', next_event->customer_->id_,
                                                0.0, 0.0, 0.0, false));

            capacity[0]++;
            if (capacity[0] == 1) {
                schedule_departure(io_queue_1.front());
            }
        } else {
            //cout << "IO1 lost" << endl;
            next_event->customer_->customer_stats_.push_back(new CustomerStats(QUEUE_IO1, sim_time, 0.0, 0.0, 0.0, true));
            next_event->customer_->lost_ = true;
            io_1_stats.push_back(new QueueStats('L', next_event->customer_->id_,
                                                0.0, 0.0, 0.0, true));
        }
    } else if (next_event->customer_->queue_type_ == QUEUE_IO2) {
        if (capacity[1] < kio) {
            next_event->customer_->arrival_time_ = sim_time;
            next_event->customer_->customer_stats_.push_back(new CustomerStats(QUEUE_IO2, sim_time, 0.0, 0.0, 0.0, false));
            io_queue_2.push_back(next_event->customer_);
            io_2_stats.push_back(new QueueStats('A', next_event->customer_->id_,
                                                0.0, 0.0, 0.0, false));
            capacity[1]++;
            if (capacity[1] == 1) {
                schedule_departure(io_queue_2.front());
            }
        } else {
            //cout << "IO2 lost" << endl;
            next_event->customer_->customer_stats_.push_back(new CustomerStats(QUEUE_IO2, sim_time, 0.0, 0.0, 0.0, true));
            next_event->customer_->lost_ = true;
            io_2_stats.push_back(new QueueStats('L', next_event->customer_->id_,
                                                0.0, 0.0, 0.0, true));
        } 
    } else if (next_event->customer_->queue_type_ == QUEUE_IO3) {
        if (capacity[2] < kio) {
            next_event->customer_->arrival_time_ = sim_time;
            next_event->customer_->customer_stats_.push_back(new CustomerStats(QUEUE_IO3, sim_time, 0.0, 0.0, 0.0, false));
            io_queue_3.push_back(next_event->customer_);
            io_3_stats.push_back(new QueueStats('A', next_event->customer_->id_,
                                                0.0, 0.0, 0.0, false));
            capacity[2]++;
            if (capacity[2] == 1) {
                schedule_departure(io_queue_3.front());
            }
        } else {
            //cout << "IO3 lost" << endl;
            next_event->customer_->customer_stats_.push_back(new CustomerStats(QUEUE_IO3, sim_time, 0.0, 0.0, 0.0, true));
            next_event->customer_->lost_ = true;
            io_3_stats.push_back(new QueueStats('L', next_event->customer_->id_,
                                                0.0, 0.0, 0.0, true));
        }
    }
}

//process the lost event
void WebServerSimulation::process_lost(EventNode *next_event) {
    if (! next_event->customer_->readd_) {
        //generate the next inter-arrival time
        Ta = expntl(1/lambda, kSeed_arrival);
        //set the simulation time to the time of the next event
        sim_time = next_event->time_;
        //create a new customer for the next arrival and add it to the event list
        Customer *customer = new Customer(total_arrival_count++, sim_time + Ta, 0.0, 0.0, 0.0, false, false); 
        customer_list.push_back(customer);
        //if the queue is not full, schedule the next event with the newly created customer
        //note that we since the customore for next_event is lost, we do not add it to the queue
        //
        //else
        //this customer is also lost and cannot be serviced
       //create a new lost event and add it to the event list
        if (n < kcpu) {
            EventNode *next_arrival = new EventNode('A', customer, sim_time + Ta);
            event_list_add(next_arrival);
        } else {
            EventNode *lost_arrival = new EventNode('L', customer, sim_time + Ta);
            customer->lost_ = true;
            customer->customer_stats_.push_back(new CustomerStats(QUEUE_CPU, sim_time, 0.0, 0.0, 0.0, true));
            cpu_stats.push_back(new QueueStats('L', customer->id_, 0.0, 0.0, 0.0, true));
            event_list_add(lost_arrival);
        }
    }
}

void WebServerSimulation::process_departure(EventNode *next_event) {
    //print_queues();
    if (next_event->customer_->queue_type_ == QUEUE_CPU) {
        if (! customer_queue.empty()) {
            Customer *departing_customer = next_event->customer_;
            sim_time = next_event->time_;
            departing_customer->departure_time_ = sim_time;
            customer_queue.pop_front();
            n--;
            if (n > 0) {
                schedule_departure(customer_queue.front());
            }

            departing_customer->customers_after_ = customer_queue.size();
           
            departing_customer->customer_stats_.back()->service_time_ = departing_customer->service_time_;
            departing_customer->customer_stats_.back()->waiting_time_ = departing_customer->departure_time_ - departing_customer->service_time_ - departing_customer->customer_stats_.back()->arrival_time_;
            departing_customer->customer_stats_.back()->departure_time_ = departing_customer->departure_time_;

            cpu_stats.push_back(new QueueStats('D', departing_customer->id_,
                        departing_customer->customer_stats_.back()->arrival_time_,
                        departing_customer->customer_stats_.back()->service_time_,
                        departing_customer->customer_stats_.back()->departure_time_,
                                               false));
            
            double selector = 1.0 * ran0(&kSeed_selector);
            EventNode *cpu_to_io_arrival = new EventNode('A', departing_customer, sim_time);
            if (selector <= 0.7) {
                departing_customer->departure_number_ = ++departure_count;
                serve_count++;
                departing_customer->serviced_ = true;
            } else if (selector <= 0.8) {
                departing_customer->queue_ = &io_queue_1;
                departing_customer->queue_type_ = QUEUE_IO1;
                event_list_add(cpu_to_io_arrival);
            } else if (selector <= 0.9) {
                departing_customer->queue_ = &io_queue_2;
                departing_customer->queue_type_ = QUEUE_IO2;
                event_list_add(cpu_to_io_arrival);
            } else {
                departing_customer->queue_ = &io_queue_3;
                departing_customer->queue_type_ = QUEUE_IO3;
                event_list_add(cpu_to_io_arrival);
            }

        } 
    } else if (next_event->customer_->queue_type_ == QUEUE_IO1) {
        if (! io_queue_1.empty()) {
            Customer *departing_customer = next_event->customer_;
            sim_time = next_event->time_;
            departing_customer->departure_time_ = sim_time;
            io_queue_1.pop_front();
            capacity[0]--; 
			if (capacity[0] > 0) {
                schedule_departure(io_queue_1.front());
            }

            departing_customer->customer_stats_.back()->service_time_ = departing_customer->service_time_;
            departing_customer->customer_stats_.back()->waiting_time_ = departing_customer->departure_time_ - departing_customer->service_time_ - departing_customer->customer_stats_.back()->arrival_time_;
            departing_customer->customer_stats_.back()->departure_time_ = departing_customer->departure_time_;

            io_1_stats.push_back(new QueueStats('D', departing_customer->id_,
                        departing_customer->customer_stats_.back()->arrival_time_,
                        departing_customer->customer_stats_.back()->service_time_,
                        departing_customer->customer_stats_.back()->departure_time_,
                                               false));

            departing_customer->readd_ = true;
            departing_customer->queue_ = &customer_queue;
            departing_customer->queue_type_ = QUEUE_CPU;
            EventNode *io_to_cpu_arrival = new EventNode('A', departing_customer, sim_time);
            event_list_add(io_to_cpu_arrival);

        }
    } else if (next_event->customer_->queue_type_ == QUEUE_IO2) {
        if (! io_queue_2.empty()) {
            Customer *departing_customer = next_event->customer_;
            sim_time = next_event->time_;
            departing_customer->departure_time_ = sim_time;
            io_queue_2.pop_front();
            capacity[1]--;
            if (capacity[1] > 0) {
                schedule_departure(io_queue_2.front());
            }
            departing_customer->customer_stats_.back()->service_time_ = departing_customer->service_time_;
            departing_customer->customer_stats_.back()->waiting_time_ = departing_customer->departure_time_ - departing_customer->service_time_ - departing_customer->customer_stats_.back()->arrival_time_;
            departing_customer->customer_stats_.back()->departure_time_ = departing_customer->departure_time_;

            io_2_stats.push_back(new QueueStats('D', departing_customer->id_,
                        departing_customer->customer_stats_.back()->arrival_time_,
                        departing_customer->customer_stats_.back()->service_time_,
                        departing_customer->customer_stats_.back()->departure_time_,
                                               false));

            departing_customer->readd_ = true;
            departing_customer->queue_ = &customer_queue;
            departing_customer->queue_type_ = QUEUE_CPU;
            EventNode *io_to_cpu_arrival = new EventNode('A', departing_customer, sim_time);
            event_list_add(io_to_cpu_arrival);

        }

    } else if (next_event->customer_->queue_type_ == QUEUE_IO3) {
        if (! io_queue_3.empty()) {
            Customer *departing_customer = next_event->customer_;
            sim_time = next_event->time_;
            departing_customer->departure_time_ = sim_time;
            io_queue_3.pop_front();
            capacity[2]--;
            if (capacity[2] > 0) {
                schedule_departure(io_queue_3.front());
            }
            departing_customer->customer_stats_.back()->service_time_ = departing_customer->service_time_;
            departing_customer->customer_stats_.back()->waiting_time_ = departing_customer->departure_time_ - departing_customer->service_time_ - departing_customer->customer_stats_.back()->arrival_time_;
            departing_customer->customer_stats_.back()->departure_time_ = departing_customer->departure_time_;

            io_3_stats.push_back(new QueueStats('D', departing_customer->id_,
                        departing_customer->customer_stats_.back()->arrival_time_,
                        departing_customer->customer_stats_.back()->service_time_,
                        departing_customer->customer_stats_.back()->departure_time_,
                                               false));

            departing_customer->readd_ = true;
            departing_customer->queue_ = &customer_queue;
            departing_customer->queue_type_ = QUEUE_CPU;
            EventNode *io_to_cpu_arrival = new EventNode('A', departing_customer, sim_time);
            event_list_add(io_to_cpu_arrival);

        }

    }
}

void WebServerSimulation::calculate_statistics() {
    QueueStats *temp;

    uint32_t total_lost = 0, total_arrival = 0;
    for (uint32_t index = 0; index < customer_list.size(); ++index) {
        for(uint32_t index2 = 0; index2 < customer_list[index]->customer_stats_.size(); ++index2) {
            total_arrival++;
            if (customer_list[index]->customer_stats_[index2]->lost_){
                total_lost++;
            }
        }
    }
    //cout << "total lost = " << total_lost << endl;
    //cout << "total arrival = " << total_arrival << endl;
	uint32_t customers_serviced = 0; 
	avg_system_time = 0;
    for (uint32_t index = 0; index < customer_list.size(); ++index) {
        if (customer_list[index]->serviced_ && 
			customer_list[index]->departure_number_ > 1000) {
			customers_serviced++;
            customer_list[index]->waiting_time_ = 0;
            for (uint32_t index2 = 0; index2 < customer_list[index]->customer_stats_.size(); ++index2) {
                customer_list[index]->waiting_time_ += customer_list[index]->customer_stats_[index2]->waiting_time_;
           }
           //cout << "customer_stat size = " << customer_list[index]->customer_stats_.size() << ", id = " << customer_list[index]->id_ << endl;
           customer_list[index]->waiting_time_ /= customer_list[index]->customer_stats_.size();
           avg_waiting_time += customer_list[index]->waiting_time_;
		   avg_system_time += customer_list[index]->departure_time_ -
		   				      customer_list[index]->arrival_time_;
        }
    }
    avg_waiting_time /= customers_serviced;
	avg_system_time /= customers_serviced;
    //avg_waiting_time /= (c - 1000);
	clr = 1.0 * total_lost / total_arrival;
    //cout << "Overall system CLR = " << 1.0 * (total_lost) / total_arrival << endl;
    //cout << "Overal system CLR = " << 1.0 * (customer_list.size() - 1000 - count) / (customer_list.size() - 1000) << endl;
    //cout << "Average waiting time = " << avg_waiting_time << endl;

    double cpu_waiting_time = 0;
    uint32_t cpu_lost = 0, cpu_arrivals = 0, cpu_serviced = 0;
    for (uint32_t index = 0; index < cpu_stats.size(); ++index) {
        if (cpu_stats[index]->type_ == 'A') {
            cpu_arrivals++;
        } else if (cpu_stats[index]->type_ == 'L') {
            cpu_lost++;
        } else if (cpu_stats[index]->type_ == 'D') {
            cpu_waiting_time += cpu_stats[index]->waiting_time_;
            cpu_serviced++;
        } 
        /*cout << cpu_stats[index]->id_ << " "
			 << cpu_stats[index]->type_ << " "
             << cpu_stats[index]->arrival_time_ << " "
             << cpu_stats[index]->service_time_ << " "
             << cpu_stats[index]->waiting_time_ << " "
             << cpu_stats[index]->departure_time_ << endl;*/
    }
    //cout << "CPU lost = " << cpu_lost << endl;
    //cout << "CPU arrival = " << cpu_arrivals << endl;
	//cout << "CPU serviced = " << cpu_serviced << endl;
    queue_avg_waiting_time[0] = 1.0 * cpu_waiting_time / cpu_serviced;
	queue_clr[0] = 1.0 * cpu_lost / (cpu_arrivals + cpu_lost);
    //cout << "CPU CLR = " << 1.0 * cpu_lost / cpu_arrivals << endl;
    //cout << "Average CPU queue waiting time = " << queue_avg_waiting_time[0] << endl;

    double io_1_waiting_time = 0;
    uint32_t io_1_lost = 0, io_1_arrivals = 0, io_1_serviced = 0;
    for (uint32_t index = 0; index < io_1_stats.size(); ++index) {
        if (io_1_stats[index]->type_ == 'A') {
            io_1_arrivals++;
        } else if (io_1_stats[index]->type_ == 'L') {
            io_1_lost++;
        } else if (io_1_stats[index]->type_ == 'D') {
            io_1_waiting_time += io_1_stats[index]->waiting_time_;
            io_1_serviced++;
        }
        /*cout << cpu_stats[index]->id_ << " "
             << cpu_stats[index]->arrival_time_ << " "
             << cpu_stats[index]->service_time_ << " "
             << cpu_stats[index]->waiting_time_ << " "
             << cpu_stats[index]->departure_time_ << endl;*/
    }
    queue_avg_waiting_time[1] = 1.0 * io_1_waiting_time / io_1_serviced;
	queue_clr[1] = 1.0 * io_1_lost / (io_1_lost + io_1_arrivals);
    //cout << "IO1 CLR = " << 1.0 * io_1_lost / io_1_arrivals << endl;
    //cout << "Average IO 1 queue waiting time = " << queue_avg_waiting_time[1] << endl;

    double io_2_waiting_time = 0;
    uint32_t io_2_lost = 0, io_2_arrivals = 0, io_2_serviced = 0;
    for (uint32_t index = 0; index < io_2_stats.size(); ++index) {
        if (io_2_stats[index]->type_ == 'A') {
            io_2_arrivals++;
        } else if (io_2_stats[index]->type_ == 'L') {
            io_2_lost++;
        } else if (io_2_stats[index]->type_ == 'D') {
            io_2_waiting_time += io_2_stats[index]->waiting_time_;
            io_2_serviced++;
        }
        /*cout << cpu_stats[index]->id_ << " "
             << cpu_stats[index]->arrival_time_ << " "
             << cpu_stats[index]->service_time_ << " "
             << cpu_stats[index]->waiting_time_ << " "
             << cpu_stats[index]->departure_time_ << endl;*/
    }

    queue_avg_waiting_time[2] = 1.0 * io_2_waiting_time / io_2_serviced;
	queue_clr[2] = 1.0 * io_2_lost / (io_2_lost + io_2_arrivals);
    //cout << "IO2 CLR = " << 1.0 * io_2_lost / io_2_arrivals << endl;
    //cout << "Average IO 2 queue waiting time = " << queue_avg_waiting_time[2] << endl;

    double io_3_waiting_time = 0;
    uint32_t io_3_lost = 0, io_3_arrivals = 0, io_3_serviced = 0;
    for (uint32_t index = 0; index < io_3_stats.size(); ++index) {
        if (io_3_stats[index]->type_ == 'A') {
            io_3_arrivals++;
        } else if (io_3_stats[index]->type_ == 'L') {
            io_3_lost++;
        } else if (io_3_stats[index]->type_ == 'D') {
            io_3_waiting_time += io_3_stats[index]->waiting_time_;
            io_3_serviced++;
        }
        /*cout << cpu_stats[index]->id_ << " "
             << cpu_stats[index]->arrival_time_ << " "
             << cpu_stats[index]->service_time_ << " "
             << cpu_stats[index]->waiting_time_ << " "
             << cpu_stats[index]->departure_time_ << endl;*/
    }
	queue_avg_waiting_time[3] = 1.0 * io_3_waiting_time / io_3_serviced;
	queue_clr[3] = 1.0 * io_3_lost / (io_3_lost + io_3_arrivals);
    //cout << "IO3 CLR = " << 1.0 * io_3_lost / io_3_arrivals << endl;
    //cout << "Average IO 3 queue waiting time = " << queue_avg_waiting_time[3] << endl;

	visits[0] = cpu_arrivals;
	visits[1] = io_1_arrivals;
	visits[2] = io_2_arrivals;
	visits[3] = io_3_arrivals;
	//visits[0] = visits[1] = visits[2] = visits[3] = 0;
	/*customers_serviced = 0;
	for (uint32_t index = 0; index < customer_list.size(); ++index) {
        if (customer_list[index]->serviced_) {
			customers_serviced++;
            for (uint32_t index2 = 0; index2 < customer_list[index]->customer_stats_.size(); ++index2) {
				CustomerStats *current = customer_list[index]->customer_stats_[index2];
				if (current->queue_type_ == QUEUE_CPU) {
					visits[0]++;
				} else if (current->queue_type_ == QUEUE_IO1) {
					visits[1]++;
				} else if (current->queue_type_ == QUEUE_IO2) {
					visits[2]++;
				} else if (current->queue_type_ == QUEUE_IO3) {
					visits[3]++;
				}
           }
        }
    }*/
	visits[0] /= c;//customers_serviced;
	visits[1] /= c;//customers_serviced;
	visits[2] /= c;//customers_serviced;
	visits[3] /= c;//customers_serviced;

	/*cout << "Average number of visits to the CPU queue = " << visits[0] << endl;
	cout << "Average number of visits to the IO 1 queue = " << visits[1] << endl;
	cout << "Average number of visits to the IO 2 queue = " << visits[2] << endl;
	cout << "Average number of visits to the IO 3 queue = " << visits[3] << endl;*/
}

void WebServerSimulation::print_queues() {
	cout << "QUEUES START" << endl;
	for (uint32_t index = 0; index < customer_queue.size(); ++index) {
		cout << customer_queue[index]->id_ << " ";
	}
	cout << endl;
	for (uint32_t index = 0; index < io_queue_1.size(); ++index) {
		cout << io_queue_1[index]->id_ << " ";
	}
	cout << endl;
	for (uint32_t index = 0; index < io_queue_2.size(); ++index) {
		cout << io_queue_2[index]->id_ << " ";
	}
	cout << endl;
	for (uint32_t index = 0; index < io_queue_3.size(); ++index) {
		cout << io_queue_3[index]->id_ << " ";
	}
	cout << endl;
}
